package edu.fim.data.dao;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.ResourceBundle;

import javax.persistence.EntityManager;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;

import org.apache.commons.beanutils.BeanUtils;

import edu.fim.data.Identifiable;
import edu.fim.data.dao.exception.DAOException;

public abstract class AbstractDAO<T extends Identifiable<K>, K extends Serializable> implements DAO<T, K> {

	private static ResourceBundle bundle = ResourceBundle.getBundle("META-INF/properties/GenericSQL");

	protected final EntityManager entityManager;
	protected final Class<T> clazz;

	protected AbstractDAO(EntityManager entityManager, Class<T> clazz) {
		this.entityManager = entityManager;
		this.clazz = clazz;
	}

	protected EntityManager getEntityManager() {
		return entityManager;
	}

	public void persist(T entity) {
		entityManager.persist(entity);
	}

	public void update(T entity) {
		T managedEntity = findById(entity.getId());
		try {
			BeanUtils.copyProperties(managedEntity, entity);
		} catch (IllegalAccessException | InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	public void delete(T entity) {
		T managedEntity = findById(entity.getId());
		entityManager.remove(managedEntity);
	}

	public T findById(K id) {
		return entityManager.find(clazz, id);
	}

	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		String sql = bundle.getString("findAll");
		sql = sql.replaceFirst("tableName", clazz.getSimpleName());
		Query query = entityManager.createQuery(sql);
		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public T findEntityByField(String field, Object value) throws DAOException {
		Query query = buildFindByField(field, value);
		try {
			return (T) query.getSingleResult();
		} catch (NonUniqueResultException e) {
			throw new DAOException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findEntitiesByField(String field, Object value) {
		Query query = buildFindByField(field, value);
		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public T findEntityByFields(String[] fields, Object[] values) throws DAOException {
		Query query = buildFindByFields(fields, values);
		try {
			return (T) query.getSingleResult();
		} catch (NonUniqueResultException e) {
			throw new DAOException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findEntitiesByFields(String[] fields, Object[] values) {
		Query query = buildFindByFields(fields, values);
		return query.getResultList();
	}
	
	@SuppressWarnings("unchecked")
	public T findEntityByQuery(String sql) {
		Query query = entityManager.createQuery(sql);
		return (T) query.getSingleResult();
	}
	
	@SuppressWarnings("unchecked")
	public List<T> findEntitiesByQuery(String sql) {
		Query query = entityManager.createQuery(sql);
		return query.getResultList();
	}

	private Query buildFindByField(String field, Object value) {
		String sql = bundle.getString("findByField");
		sql = sql.replaceFirst("tableName", clazz.getSimpleName());
		Query query = entityManager.createQuery(sql);
		query.setParameter("field", field);
		query.setParameter("value", value);
		return query;
	}

	private Query buildFindByFields(String[] fields, Object[] values) {
		String sql = bundle.getString("findByFields");
		sql = sql.replaceFirst("tableName", clazz.getSimpleName());
		StringBuilder builder = new StringBuilder(sql);
		for (int i = 1; i < fields.length; i++) {
			builder.append(" AND :field" + i);
			builder.append(" = ");
			builder.append(" :value" + i);
		}
		Query query = entityManager.createQuery(builder.toString());
		for (int i = 0; i < fields.length; i++) {
			query.setParameter("field" + i, fields[i]);
			query.setParameter("value" + i, values[i]);
			System.out.println(query.getParameterValue("field0"));
		}
		return query;
	}
}